/*
 * Copyright (C) 2012  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.resources.impl.internal;

import static nemos.resources.impl.internal.ResourcesMessage.MESSAGE_FORMAT_INVALID;
import static nemos.resources.impl.internal.ResourcesMessage.MESSAGE_KEY_MISSING;
import static nemos.resources.impl.internal.ResourcesMessage.RESOURCE_TYPE_NOT_SUPPORTED;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;

import nemos.resources.IResources;
import nemos.resources.IResourcesManager;
import nemos.resources.UnsupportedResourceType;
import nemos.util.i18n.BundleLocalization;
import nemos.util.i18n.ITranslator;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.ReferenceStrategy;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;

/**
 * TranslatorImpl : The translator standard implementation.
 * 
 * <p>
 * This implementation looks for localized messages through the
 * {@link IResourcesManager} service first. Then, it looks in the bundle
 * localization file for translation.
 * </p>
 * 
 * @author Romain DUBOIS
 */
@Service(value = ITranslator.class, serviceFactory = true)
@Reference(name = TranslatorImpl.RESOURCES_NAME, referenceInterface = IResourcesManager.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, strategy = ReferenceStrategy.LOOKUP)
@Property(name = Constants.SERVICE_PID, value = "nemos.resources.Translator")
@Component
public class TranslatorImpl implements ITranslator {

    /** The resources manager reference name */
    static final String RESOURCES_NAME = "resources"; //$NON-NLS-1$

    /** The log service */
    @Reference(name = "logService", referenceInterface = LogService.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC)
    private final AtomicReference<LogService> mLogger;

    /** The component context */
    private ComponentContext mContext;
    /** The bundle local translator */
    private BundleLocalization mLocalization;
    /** The inner translator */
    private BundleLocalization mInnerLocalization;

    /**
     * Constructor
     */
    public TranslatorImpl() {
        super();
        mLogger = new AtomicReference<LogService>();
    }

    /**
     * The component activation method.
     * 
     * @param pContext
     *            The component context
     */
    @Activate
    public void start(final ComponentContext pContext) {
        mContext = pContext;
        mLocalization = new BundleLocalization(mContext.getUsingBundle());
        mInnerLocalization = new BundleLocalization(TranslatorImpl.class);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.util.i18n.ITranslator#getLabel(java.lang.String,
     *      java.lang.Object[])
     */
    @Override
    public String getLabel(final String pKey, final Object... pParams) {
        return getText(true, null, pKey, null, pParams);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.util.i18n.ITranslator#getMessage(java.lang.String,
     *      java.lang.Object[])
     */
    @Override
    public String getMessage(final String pKey, final Object... pParams) {
        return getText(false, null, pKey, null, pParams);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.util.i18n.ITranslator#translateLabel(java.lang.String,
     *      java.lang.String, java.util.Locale, java.lang.Object[])
     */
    @Override
    public String translateLabel(final String pBaseName, final String pKey, final Locale pLocale,
            final Object... pParams) {
        return getText(true, pBaseName, pKey, pLocale, pParams);
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.util.i18n.ITranslator#translateMessage(java.lang.String,
     *      java.lang.String, java.util.Locale, java.lang.Object[])
     */
    @Override
    public String translateMessage(final String pBaseName, final String pKey, final Locale pLocale,
            final Object... pParams) {
        return getText(false, pBaseName, pKey, pLocale, pParams);
    }

    /**
     * The complete method for text access.
     * 
     * @param pUser
     *            The user flag
     * @param pBaseName
     *            The base name to use
     * @param pKey
     *            The label key
     * @param pLocale
     *            The target locale
     * @param pParams
     *            The message parameters
     * @return The required text
     */
    private synchronized String getText(final boolean pUser, final String pBaseName,
            final String pKey, final Locale pLocale, final Object... pParams) {

        final Bundle lBundle = mContext.getUsingBundle();

        // Ensure parameters are not null
        if (pKey == null) {
            throw new NullPointerException();
        }
        Locale lLocale = pLocale;
        if (lLocale == null) {
            lLocale = Locale.getDefault();
        }

        // Try the resources manager first
        String lLabel = null;
        final IResourcesManager lResourcesManager = (IResourcesManager) mContext
                .locateService(RESOURCES_NAME);
        if (lResourcesManager != null) {

            // Prepare resource names
            // First : specified resource
            final List<String> lRsrcNames = new ArrayList<String>(3);
            if (pBaseName != null) {
                lRsrcNames.add(pBaseName);
            }

            // Then : user-specific parameters
            if (pUser) {
                // Base name
                final String lUserBaseName = lResourcesManager.getUserResources();
                if (lUserBaseName != null) {
                    lRsrcNames.add(lUserBaseName);
                }

                // Locale
                if (pLocale == null) {
                    lLocale = lResourcesManager.getUserLocale();
                }
            }

            // And bundle name
            lRsrcNames.add(lBundle.getSymbolicName());

            // Collect resources
            try {
                while (lLabel == null && !lRsrcNames.isEmpty()) {
                    final IResources<String> lResources = lResourcesManager.getResources(
                            lRsrcNames.remove(0), String.class, lLocale);
                    if (lResources != null) {

                        // Then label
                        lLabel = lResources.getResource(pKey);
                    }
                }
            } catch (final UnsupportedResourceType e) {
                log(LogService.LOG_ERROR, RESOURCE_TYPE_NOT_SUPPORTED, e, e.getMessage());
            }
        }

        // Then the l10n bundle file
        if (lLabel == null) {
            lLabel = mLocalization.getValue(pKey);
        }

        // Not found : return the key
        if (lLabel == null) {
            lLabel = pKey;
            String lBaseName = pBaseName;
            if (pBaseName == null) {
                lBaseName = lBundle.getSymbolicName();
            }
            log(LogService.LOG_WARNING, MESSAGE_KEY_MISSING, null, lBaseName, pKey);
        }
        // Found : format
        else {
            try {
                final MessageFormat lFormatter = new MessageFormat(lLabel, lLocale);
                lLabel = lFormatter.format(pParams);
            } catch (final Exception e) {
                log(LogService.LOG_WARNING, MESSAGE_FORMAT_INVALID, e, lLabel);
            }
        }

        return lLabel;
    }

    /**
     * Log a message if log service is available.
     * 
     * @param pLevel
     *            The log level
     * @param pMessageKey
     *            The message key
     * @param pException
     *            The exception to log
     * @param pParams
     *            The message parameters
     */
    private void log(final int pLevel, final String pMessageKey, final Throwable pException,
            final Object... pParams) {
        final LogService lLogger = mLogger.get();
        if (lLogger != null) {
            final String lMsg = mInnerLocalization.getMessage(pMessageKey, pParams);
            lLogger.log(mContext.getServiceReference(), pLevel, lMsg, pException);
        }
    }

    /**
     * Bind the log service.
     * 
     * @param pLogService
     *            The log service
     */
    protected void bindLogService(final LogService pLogService) {
        mLogger.set(pLogService);
    }

    /**
     * Unind the log service.
     * 
     * @param pLogService
     *            The log service
     */
    protected void unbindLogService(final LogService pLogService) {
        mLogger.compareAndSet(pLogService, null);
    }
}
